#include "p2p_punch.h"
#include "chat_message.h"
#include "json.h"
#include <fstream>

P2pPunch::P2pPunch()
    : local_nat_type_(NAT_3)
    , local_ref_port_(0)
{
    stun_servers_[0] = std::make_tuple("47.97.157.56", "3478");
    //stun_servers_[1] = std::make_tuple("stun.qq.com", "3478");
    stun_servers_[1] = std::make_tuple("81.68.245.10", "3478");
}

P2pPunch::P2pPunch(std::shared_ptr<UdpClient> uc)
    : local_nat_type_(NAT_3)
    , local_ref_port_(0)
{
    vuclient_.push_back(uc);
    stun_servers_[0] = std::make_tuple("47.97.157.56", "3478");
    stun_servers_[1] = std::make_tuple("81.68.245.10", "3478");
}

void P2pPunch::detect_refletive(std::string &rip, uint16_t &rport, const std::tuple<std::string, std::string> &s)
{
    assert(!vuclient_.empty());

    char send_buf[20];
    memset(send_buf, 0, 20);
    StunMessageHeader stun_head_message(BindMethod);
    stun_head_message.encode(send_buf, 20);

    vuclient_.front()->send_data(send_buf, 20, std::get<0>(s), std::get<1>(s));
    vuclient_.front()->read_data();

    rport = vuclient_.front()->get_reflexive_port();
    rip = vuclient_.front()->get_reflexive_ip();
}

int P2pPunch::detect()
{
    std::string ip[STUN_MAX_SERVERS];
    uint16_t port[STUN_MAX_SERVERS];

    for (int i = 0; i < STUN_MAX_SERVERS; ++i) {
        detect_refletive(ip[i], port[i], stun_servers_[i]);
        if (i > 0) {
            if (ip[i] != ip[i - 1] || port[i] != port[i - 1]) {
                local_nat_type_ = NAT_4;
                break;
            }
        }
    }

    local_ref_ip_ = ip[0];
    local_ref_port_ = port[0];
    
    std::cout << "nat type: " << +local_nat_type_ << std::endl;

    return local_nat_type_;
}

int P2pPunch::notify_self_info(tcp_client &tc)
{
    std::string s;
    std::ostringstream os;
    detect();

    os << "peer" 
    << ":" << local_ref_ip_ \
    << ":" << std::to_string(local_ref_port_) \
    << ":" << std::to_string(local_nat_type_);

    s = os.str();

    std::cout << "self info:" << s << std::endl;
    chat_message msg;
    msg.body_length(s.size());
    std::memcpy(msg.body(), s.c_str(), msg.body_length());
    msg.encode_header();
    tc.write(msg);

    init_peer_mgr();
    
    std::ofstream fs;

    fs.open(local_ref_ip_, std::fstream::out);

    if (fs.is_open()) {
        fs << s << std::endl;
        fs.flush();
        fs.close();
    } else {
        std::cout << "open failed!" << std::endl;
    }
    
    return 0;
}

int P2pPunch::add_peer(const std::string &ip, const std::string &port, const std::string &nat)
{
    if (ip != local_ref_ip_ || std::stoi(port) != local_ref_port_) {
        peer_mgr_.insert(ip, port, nat);
        
        std::cout << "add ip: " << ip << "port: " << port << "nat: " << nat << std::endl;
    }

    return 0;
}

int P2pPunch::add_peer(const std::string &peer)
{
    std::string msg_type, peer_ip, peer_port, peer_nat;
    std::istringstream isstr(peer);

    std::getline(isstr, msg_type, ':');
    std::getline(isstr, peer_ip, ':');
    std::getline(isstr, peer_port, ':');
    std::getline(isstr, peer_nat, ':');



    if (msg_type != "peer") {
        std::cout << "invalid msg type: " << msg_type << std::endl;
        return 0;
    }

    add_peer(peer_ip, peer_port, peer_nat);

    return 0;
}

int P2pPunch::add_peers(const std::vector<std::string> &&peers)
{
    for (auto &&ip_port_nat : peers) {
        add_peer(ip_port_nat);
    }

    refresh_peers();

    return 0;
}

int P2pPunch::add_peers(const std::vector<std::string> &peers)
{
    for (auto &ip_port_nat : peers) {
        add_peer(ip_port_nat);
    }

    return 0;
}

void P2pPunch::refresh_peers()
{
    peer_mgr_.refresh_peers();
}

int P2pPunch::punch()
{
    assert(!vuclient_.empty());

    if (peer_mgr_.peers().empty()) {
        std::cout << "no peer!" << std::endl;
        return 0;
    } else {
        peer_mgr_.dump();
    }

    std::ostringstream ss;
    ss << chat_message::punch_header << ":" << local_ref_ip_ << ":" << local_ref_port_ << ":" << +local_nat_type_;
    
    vuclient_.front()->punch(ss.str());

    return 0;
}

int P2pPunch::notify_self_result(tcp_client &tc) {
    std::string s;
    std::ostringstream os;

    for (auto &&pi : peer_mgr_.peers()) {
        if (pi->ok() && !pi->reported()) {
            
            os << "result" \
            << ":" << local_ref_ip_ \
            << ":" << std::to_string(local_ref_port_) \
            << ":" << std::to_string(local_nat_type_) \
            << ":" << pi->ip() \
            << ":" << std::to_string(pi->res_port()) \
            << ":" << pi->nat();

            s = os.str();

            std::cout << "notify result: " << s << std::endl;
            chat_message msg;
            msg.body_length(s.size());
            std::memcpy(msg.body(), s.c_str(), msg.body_length());
            msg.encode_header();
            tc.write(msg);

            pi->reported(true);
        }
    }
    return 0;
}


